/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once
#include "../NTree.h"
#include "../Relations.h"
#include "BoxTests.h"

#include "Sphere.h"
#include "Cylinder.h"

template<class VecType>
class RoundedCube
{
private:
	const VecType vPos, vExt;
	const float r;

	//coordinates of entities
	VecType avSphereCenters[VecType::childCount];
	VecType aavEdges[VecType::childCount][VecType::dimension];

	//test objects

	//overall bounding box of the rounded cube
	Box<VecType> aabb;

	//boolean indicate whether the box has positive volume (is valid)
	bool bCenterBox;
	//box of the "inner" volume inside the chamfers
	//a box that has the corner sphere centers as corners
	Box<VecType> centerBox;

	bool abAxisBoxes[VecType::dimension];
	Box<VecType> aAxisBoxes[VecType::dimension];//axis boxes themselves

	bool abCylinders[VecType::dimension];
	Cylinder<VecType> cylinders[VecType::childCount][VecType::dimension];

	const Sphere<VecType> sphere;
	//bounding geometries for cylinders and spheres
	Box<VecType> aSphereBounds[VecType::childCount];

	//will be activated for extents smaller than 0
	bool bDegenerate;
public:
	//center vector and outer bounds
	RoundedCube(const VecType& vPos, const VecType& vExt, const float& r) :
		vPos(vPos), vExt(vExt), r(r), sphere(Sphere<VecType>(r, VecType(0))), bDegenerate(false)
	{
		//can't have a rounded cube that small. just reject everything then
		if(vExt.maxComponent() <= 1)
		{
			bDegenerate=true;
			return;
		}

		BoxTests<VecType>::ext2Box(vPos, vExt-VecType((typename VecType::scalarType)(2.0f*r)), centerBox);
		bCenterBox=BoxTests<VecType>::boxValid(centerBox);
		BoxTests<VecType>::ext2Box(vPos, vExt, aabb);
		for (unsigned int iDim = 0; iDim < VecType::dimension; iDim++)
		{
			aAxisBoxes[iDim]=centerBox;
			aAxisBoxes[iDim].max[iDim]=aabb.max.getComponent(iDim);
			aAxisBoxes[iDim].min[iDim]=aabb.min.getComponent(iDim);

			abAxisBoxes[iDim]=BoxTests<VecType>::boxValid(aAxisBoxes[iDim]);
			abCylinders[iDim]=(vExt.getComponent(iDim)>2.0f*r);
		}

		//get sphere centers
		Box<VecType> tempCanvas(vPos, VecType(0));
		for(unsigned int iBoxVertex=0; iBoxVertex<VecType::childCount; iBoxVertex++)
		{
			//center points of the spheres
			avSphereCenters[iBoxVertex]=BoxTests<VecType>::getBoxVertex(centerBox, iBoxVertex);
			//get bounding boxes for the spheres (so they don't extend beyond their sector)
			Box<VecType> sphereBox(avSphereCenters[iBoxVertex]-VecType((typename VecType::scalarType)(r)),
					avSphereCenters[iBoxVertex]+VecType((typename VecType::scalarType)(r)));
			//let the canvas be the sector of the rounded cube for this sphere
			//the rounded cube effectively has eight sectors, divided by the center point
			//of the cube. each corner sphere therefore controls its own sector of the cube
			tempCanvas.max=BoxTests<VecType>::getBoxVertex(aabb, iBoxVertex);
			//order the components correctly
			Box<VecType> canvas=BoxTests<VecType>::correctBox(tempCanvas);

			//truncate the sphere bounding box to the sector size
			//it won't be changed if it is entirely inside the sector
			aSphereBounds[iBoxVertex]=BoxTests<VecType>::canvasBox(sphereBox, canvas);

			//just a safety check
			vAssert(BoxTests<VecType>::boxValid(aSphereBounds[iBoxVertex]));

			for (unsigned int iDim = 0; iDim < VecType::dimension; iDim++)
			{
				VecType vEdge=avSphereCenters[iBoxVertex];
				if(vEdge.getComponent(iDim)==centerBox.min.getComponent(iDim))
					vEdge[iDim]=centerBox.max.getComponent(iDim);
				else
					vEdge[iDim]=centerBox.min[iDim];

				if(abCylinders[iDim])
				{
					const float fLength=(const float)(vEdge-avSphereCenters[iBoxVertex]).getComponent(iDim);
					Cylinder<VecType> cyl(r, fLength, iDim);
					memcpy(&(cylinders[iBoxVertex][iDim]), &cyl, sizeof(Cylinder<VecType>));//HACK!
				}
			}
		}
	}

	bool
	testPoint(const VecType& v) const
	{
		//reject queries if cube is degenerate
		if(bDegenerate)
			return false;

		//first check: aabb
		if(!BoxTests<VecType>::isInsideBox(aabb, v))
			return false;

		//check against axis aligned boxes
		for(unsigned int iAABox=0; iAABox<VecType::dimension; iAABox++)
		{
			if(abAxisBoxes[iAABox])
				if(BoxTests<VecType>::isInsideBox(aAxisBoxes[iAABox], v))
					return true;
		}
		//get quadrant for further checks
		const int iQuadrant=BoxTests<VecType>::getQuadrant(vPos, v);
		//check against sphere
		if(sphere.testPoint(v-avSphereCenters[iQuadrant])==Inside)
			return true;

		//check against cylinders
		//cylinder start points are sphere centers
		//cylinders start from each inner box vertex and point in every axis dir.
		//quadrant is known
		//3 cylinders cast from that quadrant point
		for(unsigned int iCylinder=0; iCylinder<VecType::dimension; iCylinder++)
		{
			if(abCylinders[iCylinder])
				if(cylinders[iQuadrant][iCylinder].testPoint(v-avSphereCenters[iQuadrant]))
					return true;
		}
		return false;
	}


	RangeRelation
	testBox(const Box<VecType>& box) const
	{
		//reject queries if cube is degenerate
		if(bDegenerate)
			return Outside;

		RangeRelation bbRel=BoxTests<VecType>::boxRelation(box, aabb);
		//bb check
		switch(bbRel)
		{
		case Outside:
			return Outside;
		//check if we are inside the box
		case Surrounds:
			return Surrounds;
		default:
			break;
		}
		//test sub-geometries
		//first: point test

		unsigned int nInside=0,
						nOutside=0;
		for(unsigned int iCorner=0; iCorner<VecType::childCount;iCorner++)
		{
			if(nInside!=iCorner && nOutside!=iCorner)
				break;//mixed case

			if(testPoint(BoxTests<VecType>::getBoxVertex(box, iCorner)))
				nInside++;
			else
				nOutside++;
		}
		if(nInside==VecType::childCount)
			return Inside;
		if(nOutside!=VecType::childCount)
			return Overlaps;//mixed points

		//now do the hard stuff: plane tests
		//essentially the same as point tests
		//only overlaps/outside-test has to be performed here


		//check against axis aligned boxes
		for(unsigned int iAABox=0; iAABox<VecType::dimension; iAABox++)
		{
			if(!abAxisBoxes[iAABox])
				continue;
			if(BoxTests<VecType>::boxRelation(box, aAxisBoxes[iAABox])!=Outside)
				return Overlaps;
		}

		for (unsigned int iSphere = 0; iSphere < VecType::childCount; iSphere++)
		{
			//clip box
			Box<VecType> testBox=
					BoxTests<VecType>::canvasBox(box, aSphereBounds[iSphere]);
			//check whether anything of the box to test is actually inside the sphere's bounds
			if(!BoxTests<VecType>::boxValid(testBox))
				continue;
			Box<VecType> translatedBox(testBox.min-avSphereCenters[iSphere],
					testBox.max-avSphereCenters[iSphere]);
			if(sphere.testBox(translatedBox)!=Outside)
				return Overlaps;
		}
/*		//check against cylinders
		//cylinder start points are sphere centers
		//cylinders start from each inner box vertex and point in every axis dir.
		//quadrant is known
		//3 cylinders cast from that quadrant point
		for(int iCylinder=0; iCylinder<VecType::dimension; iCylinder++)
		{
			if(!abCylinders[iCylinder])
				continue;
			if(cylinders[iQuadrant][iCylinder].checkPoint(v-avSphereCenters[iQuadrant]))
				return true;
		}
		*/
		return Outside;
	}
	Box<VecType>
	getBounds() const
	{
		return aabb;
	}
};

template<class VecType>
class RoundedCubeCon
{
public:
	RoundedCubeCon(const VecType& vPos, const VecType& vExt, const float& r) :
		rc(vPos, vExt, r) , size( AuxMath::log2( vExt.maxComponent()/2 + 1 + vPos.maxComponent() + 1)) {}
	const int getSize() const {return size;}

	const VoxelType fn(const Box<VecType>& box) const
	{
		return relationToVoxel(rc.testBox(box));
	}
private:
	RoundedCube<VecType> rc;
	int size;
};
